package cn.good.yan.service.t1;

import com.sun.corba.se.impl.presentation.rmi.IDLTypesUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 1 测试 Redis - 分布式
 * sale  本地单机锁
 * saleA 使用分布式锁 setnx 用while判断，防止虚假唤醒，也不适用递归
 * saleB 分布式锁 setnx + 锁的过期时间
 * saleC 分布式锁 setnx + 只能删除自己的锁
 * saleD 分布式锁 setnx + 使用 Lua 脚本 保证原子性
 * saleED1 分布式锁 ， 使用事务处理
 * 但满足单机的Redis，如果更完善，执行可重入锁和看门狗机制
 * 在 OrderService2
 * <p>
 * lock.tryLock() 尝试加锁
 *
 * @author shengren.yan
 * @create 2023-11-08
 */
@Service
@Slf4j
public class OrderService {

    @Resource
    private RedisTemplate redisTemplate;
    // 判断哪台机器
    @Value("${server.port}")
    private String port;

    private Lock lock = new ReentrantLock();

    // 1. 本机的锁。不是分布式的锁 - 容易超卖问题
    public String sale() {
        String retMessage = "";
        lock.lock();
        try {
            //1 查询库存信息  该 sale100 的key在redis中有100个库存
            String result = (String) redisTemplate.opsForValue().get("sale100");
            //2 判断库存是否足够
            Integer num = result == null ? 0 : Integer.parseInt(result);
            //3 扣减库存
            if (num > 0) {
                redisTemplate.opsForValue().set("sale100", String.valueOf(--num));
                retMessage = "成功卖出一个商品，库存剩余: " + num;
                System.out.println(retMessage);
            } else {
                retMessage = "商品卖完了，o(╥﹏╥)o";
            }
        } finally {
            lock.unlock();
        }

        // 尝试加锁，tryLock加锁 (lock.tryLock()
//        try {
//            // 尝试加锁，抢3分，时间太长就放弃，抢锁
//            if (lock.tryLock(3L,TimeUnit.SECONDS)) {
//                try {
//                    // 业务逻辑
//                    System.out.println("-----逻辑----");
//                } finally {
//                    lock.unlock();
//                }
//            }else {
//                // 没有抢到锁，处理的逻辑，可以再递归该方法。
//            }
//        } catch (InterruptedException e) {
//            throw new RuntimeException(e);
//        }

        return retMessage + "\t" + "服务端口号：" + port;
    }


    /**
     * 2  分布式锁 setnx  用while判断
     * 这部分也有问题，当我们这台服务挂了，服务A上了锁，没有加过期时间，所以一直会被锁住，其他服务抢不到锁
     */
    public String saleA() {
        String retMessage = "";
        String key = "分布式的key";
        // 分布式的key 对应的value
        String value = UUID.randomUUID() + ":" + Thread.currentThread().getId();

        // 分布式锁  不建议使用递归，递归在高并发中很容易栈溢出，我们用自旋替代递归方法调用；也不用if，用while来替代
        while (!redisTemplate.opsForValue().setIfAbsent(key, value)) {
            try {
                Thread.sleep(20);  // 暂时 20秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 抢到锁，进行往下操作，扣减库存
        try {
            //1 查询库存信息  商品库存的key
            String result = (String) redisTemplate.opsForValue().get("sale100");
            //2 判断库存是否足够
            Integer num = result == null ? 0 : Integer.parseInt(result);
            //3 扣减库存
            if (num > 0) {
                redisTemplate.opsForValue().set("sale100", String.valueOf(--num));
                retMessage = "成功卖出一个商品，库存剩余: " + num;
                System.out.println(retMessage);
            } else {
                retMessage = "商品卖完了，o(╥﹏╥)o";
            }
        } finally {
            redisTemplate.delete(key);
        }
        return retMessage + "\t" + "服务端口号：" + port;
    }

    /**
     * 3  分布式锁 setnx  用while判断，并且给加锁增加过期时间
     * 这部分也有问题，因为服务A 进行加锁，它执行慢了，然后锁自动过期，
     * 服务B 进行加锁，A服务执行完，进行解决锁操作，顺便把服务B上的锁也释放了，
     * 可服务B并没有执行完，有问题
     */
    public String saleB() {
        String retMessage = "";
        String key = "分布式的key";
        // 分布式的key 对应的value
        String value = UUID.randomUUID() + ":" + Thread.currentThread().getId();
        // 分布式锁  这里我们把分布式锁加了30秒过期时间
        while (!redisTemplate.opsForValue().setIfAbsent(key, value, 30L, TimeUnit.SECONDS)) {
            try {
                Thread.sleep(20);  // 暂时 20秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 抢到锁，进行往下操作，扣减库存
        try {
            //1 查询库存信息  商品库存的key
            String result = (String) redisTemplate.opsForValue().get("sale100");
            //2 判断库存是否足够
            Integer num = result == null ? 0 : Integer.parseInt(result);
            //3 扣减库存
            if (num > 0) {
                redisTemplate.opsForValue().set("sale100", String.valueOf(--num));
                retMessage = "成功卖出一个商品，库存剩余: " + num;
                System.out.println(retMessage);
            } else {
                retMessage = "商品卖完了，o(╥﹏╥)o";
            }
        } finally {
            redisTemplate.delete(key);
        }
        return retMessage + "\t" + "服务端口号：" + port;
    }

    /**
     * 4  分布式锁 setnx  用while判断，只能删除自己的锁，其他线程删除不了
     * 这部分也有问题，因为判断和删除并不是原子性操作
     */
    public String saleC() {
        String retMessage = "";
        String key = "分布式的key";
        // 分布式的key 对应的value
        String value = UUID.randomUUID() + ":" + Thread.currentThread().getId();
        // 分布式锁  这里我们把分布式锁加了30秒过期时间
        while (!redisTemplate.opsForValue().setIfAbsent(key, value, 30L, TimeUnit.SECONDS)) {
            try {
                Thread.sleep(20);  // 暂时 20秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 抢到锁，进行往下操作，扣减库存
        try {
            //1 查询库存信息  商品库存的key
            String result = (String) redisTemplate.opsForValue().get("sale100");
            //2 判断库存是否足够
            Integer num = result == null ? 0 : Integer.parseInt(result);
            //3 扣减库存
            if (num > 0) {
                redisTemplate.opsForValue().set("sale100", String.valueOf(--num));
                retMessage = "成功卖出一个商品，库存剩余: " + num;
                System.out.println(retMessage);
            } else {
                retMessage = "商品卖完了，o(╥﹏╥)o";
            }
        } finally {
            // 只能当前线程删除自己的锁  - 但也有问题，因为判断和删除并不是原子性操作
            String key1 = (String) redisTemplate.opsForValue().get(key);
            if (key1.equalsIgnoreCase(value)) {
                redisTemplate.delete(key);
            }
        }
        return retMessage + "\t" + "服务端口号：" + port;
    }

    /**
     * 5  分布式锁 setnx   使用 Lua 脚本 - 保证原子性
     * 但还是有问题，可重入锁问题
     */
    public String saleED() {
        String retMessage = "";
        String key = "分布式的key";
        // 分布式的key 对应的value
        String value = UUID.randomUUID() + ":" + Thread.currentThread().getId();
        // 分布式锁  这里我们把分布式锁加了30秒过期时间
        while (!redisTemplate.opsForValue().setIfAbsent(key, value, 30L, TimeUnit.SECONDS)) {
            try {
                Thread.sleep(20);  // 暂时 20秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 抢到锁，进行往下操作，扣减库存
        try {
            //1 查询库存信息  商品库存的key
            String result = (String) redisTemplate.opsForValue().get("sale100");
            //2 判断库存是否足够
            Integer num = result == null ? 0 : Integer.parseInt(result);
            //3 扣减库存
            if (num > 0) {
                redisTemplate.opsForValue().set("sale100", String.valueOf(--num));
                retMessage = "成功卖出一个商品，库存剩余: " + num;
                System.out.println(retMessage);
            } else {
                retMessage = "商品卖完了，o(╥﹏╥)o";
            }
        } finally {
            // 改进点，修改为Lua脚本的Redis分布式锁调用，必须保证原子性
            String luaScript =
                    "if (redis.call('get',KEYS[1]) == ARGV[1]) then " +
                            "return redis.call('del',KEYS[1]) " +
                            "else " +
                            "return 0 " +
                            "end";
            // 有返回值 Boolean 和Long 都可以，key代表 当前分布式锁的key。value就是该值
            redisTemplate.execute(new DefaultRedisScript(luaScript, Boolean.class), Arrays.asList(key), value);
        }
        return retMessage + "\t" + "服务端口号：" + port;
    }

    /**
     * 5  分布式锁 setnx   使用 事务，不使用Lua脚本- 保证原子性
     * 但还是有问题，可重入锁问题
     */
    public String saleED1() {
        String retMessage = "";
        String key = "分布式的key";
        // 分布式的key 对应的value
        String value = UUID.randomUUID() + ":" + Thread.currentThread().getId();
        // 分布式锁  这里我们把分布式锁加了30秒过期时间
        while (!redisTemplate.opsForValue().setIfAbsent(key, value, 30L, TimeUnit.SECONDS)) {
            try {
                Thread.sleep(20);  // 暂时 20秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 抢到锁，进行往下操作，扣减库存
        try {
            //1 查询库存信息  商品库存的key
            String result = (String) redisTemplate.opsForValue().get("sale100");
            //2 判断库存是否足够
            Integer num = result == null ? 0 : Integer.parseInt(result);
            //3 扣减库存
            if (num > 0) {
                redisTemplate.opsForValue().set("sale100", String.valueOf(--num));
                retMessage = "成功卖出一个商品，库存剩余: " + num;
                System.out.println(retMessage);
            } else {
                retMessage = "商品卖完了，o(╥﹏╥)o";
            }
        } finally {
            // 自循环
            while (true) {
                // 给键值进行监控，相当于redis的乐观锁，如果 我自己删除，别人不用动，如果动了，会重新进该循环
                redisTemplate.watch(key);
                String o = (String) redisTemplate.opsForValue().get(key);
                // 自己的线程删除自己锁
                if (o.equalsIgnoreCase(value)) {
                    redisTemplate.setEnableTransactionSupport(true);
                    // 开启事务
                    redisTemplate.multi();
                    // redis的逻辑
                    redisTemplate.delete(key);
                    // 提交事务，如果返回null，代表提交失败
                    List<Object> exec = (List<Object>) redisTemplate.exec();
                    if (exec == null) {
                        continue;
                    }
                }
                redisTemplate.unwatch();
                break;
            }
        }
        return retMessage + "\t" + "服务端口号：" + port;
    }


}
